/**
 
Uebung 4 - Kugelpendel
=======================

Dynamisches System - behandelt beliebige Systeme und deren zeitliche Entwicklung durch
Aenderungsraten

Alle Newton-ische Systeme gehören zu dieser Klasse

Zustand des Systems zum einem bestimmten Zeitpunkt t wird duch einen
n - wertigen reelen Vektor beschrieben = Zustandsvektor

==> 
zeitliche Entwicklung also die Aenderungsrate des Zustandsvektors wird durch ein
Vektorfeld in R^n beschrieben

Vektorfeld gibt die
	-Richtung
	-Aederungsrate des Zustandsvektors
von x(t) vor

Vektorfeld = Funktion in R^n: ordnet jedem Punkt im Gebiet einen Vekto y = f(x) zu
wobei f(x) ∈ R^n  

Beispiel x(t) ∈ R^2 ---> x = ( 	x1 )  folglich y ∈ R^2 ---> y = f(x) = ( f(x1) )
								x2										 f(x2)

Berechnung von Integralkurven: Mit Euler oder Runge-Kutta

Euler:
Integralkurve wird nährungsweise als Streckenzug ausgehend vom Startpunkt x0 berechnet:
	y = f(x)
	x' = x + y * dt
	t' = t + dt

Es verwendet die Nährung, dass der Richtungsvektor y (Geschwindigkeit) im Zeitintervall dt konstant ist

Kutta: siehe Skript

 **/
package pendel;

import gui.DoubleProperty;
import gui.IUebung;

import javax.media.opengl.*;
import javax.media.opengl.glu.GLU;


import com.sun.opengl.util.FPSAnimator;
import com.sun.opengl.util.GLUT;

import java.awt.*;
import java.awt.event.*;
public class KugelPendel extends Dynamics
       implements WindowListener, GLEventListener, KeyListener, IUebung
{	
	private GLCanvas canvas;		//OpenGL-Canvas
	
	private int win_width;
	private int win_height;
	
	private double xmin = -0.4;
	private double xmax = 0.4;
	private double ymin	;
	private double ymax;
	//viewing volumen vergroessen, heist clipping bereich vergroessen
	double zmin = -100, zmax = 100; // Clipping Bereich
	double elev = 20; // Elevation Kamera-System
	double azim = 30; // Azimut Kamera-System	
	
	private DoubleProperty elevproperty = new DoubleProperty("Elevation", -360d, 360d, 0.1d, 20d);
	private DoubleProperty azimproperty = new DoubleProperty("Azimut", -180d, 180d, 0.1d, 30d);
	
	//GraviatationsKonstante
	private double G = 0.1;			
	//erdbeschleunigung
	private double g = 9.81;
	//Fadenlaenge
	private double l = 0.35;
	//Kugelmasse
	private double m = 0.1;
	//Kugelradius
	private double r= 0.01;
	//Konstante fuer Luftwiderstand
	private double k = 0.05;
//	private double k = 0.4;
	//Magnete
	private double K = 0.001;
	//Seite des Dreiecks
	private double s = 0.1;
	//Hoehe des gleichseitendreiecks
	private double h = s * Math.sqrt(3)/2;
	//Magnete sollten leicht unterhalb x,y liegen, d = Abstand Magnete von Ebene
	double d = 0.008;
	
	DoubleProperty init_x = new DoubleProperty("X-Pos", -0.2, 0.2, 0.01, -0.1,1000);
	DoubleProperty init_y = new DoubleProperty("Y-Pos", -0.2, 0.2, 0.01,  0.08,1000);
	DoubleProperty init_vx = new DoubleProperty("Vx", -0.4, 0.4, 0.01, 0.1,1000);
	DoubleProperty init_vy = new DoubleProperty("Vy", -0.4, 0.4, 0.01, 0.3,1000);
	
	//1 Ausbau: v integrieren in x
	//double[] x = {x,y,vx,vy}
	double[] x = { -0.1 , 0.08, 0.1, 0.3 };
	
	double z;
	
	//Zeitschritt
	private double dt = 0.002;
	
	private FPSAnimator anim;
	private GLU glu;
	
	private GLUT glut = new GLUT();
	
	private boolean log = false;
	
	private boolean run = false;
	
	//2. Ausbau
	
	Magnet m1 = new Magnet (-s/2, -h/3, -d,K);
	Magnet m2 = new Magnet (s/2, -h/3, -d,K);
	Magnet m3 = new Magnet (0 , 2*h/3, -d,K);
	
	/** 
		Bewegungsgleichung ohne Luftwiderstand
		m * a = F + m * g
		Die Fadenrkaft F hat daide Rihtungs des Vektors PS (P Position des Pendels zur Zeit Z, S; Aufhängepunkt)
		F = c * PS = c * ( -x, -y , l - z ) --> Problem Kraft F ist unbekannt, kann F theoretisch eleminieren indem
		man die Bewegungsgleichung in Richtung senkrecht zu F (Tangentialebene) betrachtet, ist aber mathematisch aufwendig
		
		Nährung für kleine Auslenkung ( z ca 0)
		Kräftegleichgewicht: d.h. z Komponenten von F kompensiert mg)
		Fz = m * g
		
		mit Fz = c * PS = c * l --> m * g = c * l --> c = mg / l
		
		Kräfte:
		
		Fx = - mg/l * x			Fy = - mg/l * y			-Minus weil Fz nach oben und mg nach unten
	 **/
	
	/**1. Ausbau
		Methode move mit Klasse Dynamics ersetzen
		x = ( x,y,vx,vy) und moechten y = f(x) = ( vx,vy,fx/m,fy,m)
		man uebergibt f den vektor x und liefert den y vektor zurueck 
	**/
	
	public double[] f(double[] x){
		double vx = x[2];
		double vy = x[3];
		
		/**	
		 	Original
			double[] y = { vx, vy, -g*x[0]/l - k*vx/m, -g*x[1]/l - k*vy/m};
			2. Ausbau
			Einbau der Magnete in die Bewegungsgleichungen 
		**/
		
		
		double[] pos = { x[0], x[1], z(x[0],x[1]) };
		double[] F1 = m1.feld(pos);
		double[] F2 = m2.feld(pos);
		double[] F3 = m3.feld(pos);
		double[] y = { vx, vy, 
				-g*x[0]/l - k*vx/m + (F1[0] + F2[0] + F3[0])/m, 
				-g*x[1]/l - k*vy/m + (F1[1] + F2[1] + F3[1])/m,};
		return y;

	}

	
	public void reset(){
		x[0] = init_x.getCurrent();
		x[1] = init_y.getCurrent();
		x[2] = init_vx.getCurrent();
		x[3] = init_vy.getCurrent();
	}
	
	public DoubleProperty[] getProperties(){
		DoubleProperty[] res = {elevproperty,azimproperty,init_x,init_y,init_vx,init_vy};
		return res;
	}
	
	
	
	//2. Ausbau ---------- Hilfsklasse ------------------
	static class Magnet{
		//Konstruktor erhaelt die Koordinaten des Magneten
		//Position
		double[] x = new double[3];
		//staerke des magneten
		double K;

		Magnet(double x, double y, double z, double K){
			this.x[0] = x;
			this.x[1] = y;
			this.x[2] = z;
			this.K = K;
		}
		
		double[] getPos(){
			double[] res = {x[0],x[1],x[2]};
			return res;
		}
		
		public double[] feld(double[] pos){
			double r[] = { x[0] - pos[0], x[1] - pos[1], x[2] - pos[2]};
			//berechung der norm des vektors r
			double rnorm = Math.sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2]);
			double factor = K / (rnorm*rnorm*rnorm);
			//vektor des magnetfelds
			double[] y = {factor*r[0], factor*r[1], factor*r[2]};
			return y;
		}
	}
    
	//  ------------------  Methoden  --------------------
	/**
	 * Berechnung der Bahnkurve naehrungsweise in kleinen Zeitschritten
	 * nach Algorithmus von Euler
	 * Beschleunigung a wird mit dem gegebenen Kraftgesetz berechnet
	 * gemass a = F /m
	 * @param pos x , y im array x, als Position
	 * @return Vektor der Kreisbahn
	 */
	void move (double[]x, double[] v, double dt){
		double[] a = { -g * x [0]/l - k * v[0],
						-g * x [1]/l - k * v[1]};
		x[0] += v[0] * dt;
		x[1] += v[1] * dt;
		v[0] += a[0] * dt;
		v[1] += a[1] * dt;

	}
	
	/**
	 * Berechnet die z Koordinate auf der Kugel
	 * @param x
	 * @param y
	 * @return
	 */
	double z(double x, double y){
		double tmp = l * l - x * x * - y *y ;
		if ( l <= tmp ){
			return 0;
		}
		else {
			return l - Math.sqrt(tmp);
		}
	}
	
	// Koordinatenachsen zeichnen
	void zeichneAchsen(GL gl) {
		gl.glBegin(GL.GL_LINES);
		// x-Achse
		gl.glVertex3d(xmin/2, 0,0);
		gl.glVertex3d(xmax/2, 0,0);
		// y-Achse
		gl.glVertex3d(0, -ymax/2,0);
		gl.glVertex3d(0, ymax/2,0);
		// z-Achse
		gl.glVertex3d(0, 0,0);
		gl.glVertex3d(0, 0,0.2);
		
		// x-Achse
		gl.glVertex3d(xmin/2, 0,0.2);
		gl.glVertex3d(xmax/2, 0,0.2);
		// y-Achse
		gl.glVertex3d(0, -ymax/2,0.2);
		gl.glVertex3d(0, ymax/2,0.2);
		
		gl.glEnd();
		
	}
	//Zeichne Box
	void zeichneBox(GL gl){
		gl.glBegin(GL.GL_LINE_LOOP);
		double lenght = 0.12;
		//Boden
		gl.glVertex3d(lenght,-lenght,0);
		gl.glVertex3d(lenght, lenght,0);
		gl.glVertex3d(-lenght,lenght,0);
		gl.glVertex3d(-lenght, -lenght,0);
		gl.glVertex3d(lenght,-lenght,0);
		gl.glEnd();
		//Deckel
		gl.glBegin(GL.GL_LINE_LOOP);
		gl.glVertex3d(lenght,-lenght,0.2);
		gl.glVertex3d(lenght, lenght,0.2);
		gl.glVertex3d(-lenght,lenght,0.2);
		gl.glVertex3d(-lenght, -lenght,0.2);
		gl.glVertex3d(lenght,-lenght,0.2);
		gl.glEnd();
		//seitliche Kanten
		gl.glBegin(GL.GL_LINES);
		gl.glVertex3d(lenght,-lenght,0);
		gl.glVertex3d(lenght,-lenght,0.2);
		gl.glVertex3d(lenght, lenght,0);
		gl.glVertex3d(lenght, lenght,0.2);
		gl.glVertex3d(-lenght,lenght,0);
		gl.glVertex3d(-lenght,lenght,0.2);
		gl.glVertex3d(-lenght, -lenght,0);
		gl.glVertex3d(-lenght, -lenght,0.2);
		gl.glEnd();
		
	}


     // Konstruktor
     public KugelPendel()
     {  
    	Frame f = new Frame("KugelPendel");
        f.setSize(800, 600);
        f.addWindowListener(this);
        // OpenGL-Window
        canvas = new GLCanvas();
        canvas.addKeyListener(this);
        canvas.addGLEventListener(this);
        f.add(canvas);
        f.setVisible(true);
        canvas.requestFocus();
     }
     
     public KugelPendel(GLJPanel jp){
         jp.addKeyListener(this);
         jp.addGLEventListener(this);
         jp.setVisible(true);
         jp.requestFocus();
     }


     public static void main(String[] args)                     // main-Methode der Applikation
     {  new KugelPendel();
     }


     //  ---------  OpenGL-Events  -----------------------

	public void init(GLAutoDrawable drawable) {
		//OpenGL-Objekt
		GL gl = drawable.getGL();
		glu = new GLU();
		//erasing color
		gl.glClearColor(0.68f, 0.84f, 0.9f, 1.0f);
		
		//-----Sichtbarkeit-----------
		
		//aktivieren des z Buffers
		gl.glEnable(GL.GL_DEPTH_TEST);
		
		//aktivieren des Polygon Offset
		gl.glEnable(GL.GL_POLYGON_OFFSET_FILL);
		
		//zu den Z-Werten wir ein Offset hinzugefuegt, da für interpolierte z-Werte auf den Randlinien leicht
		//unterschiedliche Z-Werte entsehen können, obwohl es sich um die selben z-Werte handelt. Führt dazu,
		//dass der Depth-Test nicht richtig funktioniert --> kann mit Polygon-Offset vermieden werden (wird
		//zusätzlich zur Z-Koordinate dazu gerechnet (Skript S.28)
		gl.glPolygonOffset(1,1);
		
		//-----Beleuchtung------------
		
		//Beleuchtung aktivieren
		gl.glEnable(GL.GL_LIGHTING);
		//Wichtig: so das alle Richtungen normalisiert werden
		gl.glEnable(GL.GL_NORMALIZE);
		//Lichtquellen einschalten, positionierung in display methode
		//Aktiviert die 0 Lichtquelle
		gl.glEnable(GL.GL_LIGHT0);
		//ShadeModel einfuegen, default ist es smooth, flat ist schneller
		//bei flat hat eine flaeche, genau eine helligkeit, bei smooth werden die lichtpunkte
		//innerhalb einer flaeche noch interpoliert.
		gl.glShadeModel(GL.GL_SMOOTH);
		//diffusen koeffizienten komponenten, 3 rgb und durchsichtigkeit
		float[] werte = {0.6f, 0.6f, 0f, 1f};
		//hinten relativ dunkel -> muss streulicht erhoehen
		gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, werte,0);
		
		anim = new FPSAnimator(drawable, 200, true);

		anim.start();
	}


	public void display(GLAutoDrawable drawable) {
		GL gl = drawable.getGL();
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
				
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
		gl.glMatrixMode(GL.GL_MODELVIEW);
		gl.glLoadIdentity();
		
		for ( int i = 0 ; i < 2; ++i){
			if ( i == 0){
				gl.glClearColor(0.68f, 0.84f, 0.9f, 1.0f);
				gl.glMatrixMode(GL.GL_PROJECTION);
				gl.glLoadIdentity();
				gl.glOrtho(xmin, xmax, ymin, ymax, zmin, zmax);
				gl.glViewport(0, 0, win_width, win_height);
				// Kamera-System positionieren:
				// Kamera vom System weg bewegen, hat aber fuer die orthogonale
				// Projektion keine Wirkung, erst wenn die eleveation dazu kommt spielt
				// dies eine Rolle
				gl.glTranslated(0, 0, 0.1);
				// wichtig elevation nach dem translate;
				// notizen: ensrpicht der 1 Variante: absolute Variante
				gl.glRotated(elevproperty.getCurrent(), 1, 0, 0); // Drehung um x-Achse
				gl.glRotated(-azimproperty.getCurrent(), 0, 1, 0); // Drehung um y-Achse
			} else{
				gl.glMatrixMode(GL.GL_PROJECTION);
				gl.glLoadIdentity();
				gl.glOrtho(xmin, xmax, ymin, ymax, zmin, zmax);
				gl.glViewport(win_width/2, 0, win_width/2, win_height/2);
				gl.glRotated(90, 1, 0, 0); // Drehung um x-Achse
				gl.glRotated(-azimproperty.getCurrent(), 0, 1, 0); // Drehung um y-Achse
				
			}
			// Achsen sollen nicht beleuchtet sein
			gl.glDisable(GL.GL_LIGHTING);
			gl.glColor3d(0, 0, 0.1);

			// Hier Position der Kamera, muss die Koordinaten in einem Float Array
			// vorbereiten
			float[] pos = { 10, 10, 10, 1 };
			// Die Koordinaten des Lichts werden in das Kamerasystem umgerechnet
			// bewege ich nur die Kamera, dann haben wir eine andere Beleuchtung
			gl.glLightfv(gl.GL_LIGHT0, GL.GL_POSITION, pos, 0);
			gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
			
			//Objektsystem drehen da in OpenGL die Koordinatenachsen anders sind
			//Drehung um die X-Achse
			gl.glRotated(-90,1,0,0);
			//Drehumg um die z Achse
			gl.glRotated(-90,0,0,1);
			
			zeichneAchsen(gl);

			// Aufhaengepuntk
			gl.glPushMatrix();
			gl.glTranslated(0,0,0.2);
			gl.glColor3d(1,1,0.1);
			glut.glutSolidSphere(0.2*r,20,12);
			gl.glPopMatrix();
			
			//Zeichne Magneten
			gl.glPushMatrix();
			gl.glColor3d(1,0,0.1);
			gl.glTranslated(m1.getPos()[0], m1.getPos()[1], m1.getPos()[2]);
			glut.glutSolidSphere(r/3, 10, 4);
			gl.glTranslated(-m1.getPos()[0], -m1.getPos()[1], -m1.getPos()[2]);
			gl.glTranslated(m2.getPos()[0], m2.getPos()[1], m2.getPos()[2]);
			glut.glutSolidSphere(r/3, 10, 4);
			gl.glTranslated(-m2.getPos()[0], -m2.getPos()[1], -m2.getPos()[2]);
			gl.glTranslated(m3.getPos()[0], m3.getPos()[1], m3.getPos()[2]);
			glut.glutSolidSphere(r/3, 10, 4);
			gl.glColor3d(1,1,0.1);

			gl.glPopMatrix();
			
			zeichneBox(gl);
			
			//Faden
			z = z(x[0],x[1]);
			gl.glBegin(gl.GL_LINES);
			gl.glVertex3d(0,0,0.2);
			gl.glVertex3d(x[0],x[1],z);
			gl.glEnd();
			//fuer kugel
			gl.glTranslated (x[0], x[1], z);
			//dann solid sphere zeichnen
			glut.glutSolidSphere(r,20,12);
			
			
			
			if (run){
				//move(x, v, dt);
				runge(x, dt);		
			}else {
				x[0] = init_x.getCurrent();
				x[1] = init_y.getCurrent();
				x[2] = init_vx.getCurrent();
				x[3] = init_vy.getCurrent();
			}
	        // Change back to model view matrix.
	        gl.glMatrixMode(GL.GL_MODELVIEW);
	        gl.glLoadIdentity();
	        gl.glClear(GL.GL_DEPTH_BUFFER_BIT);
		}
	
	}

	/*
	 * reshpae wird aufgerufen sobald das Program-Window in der Grösse verändert wird
	 */
	public void reshape(GLAutoDrawable drawable, int x, int y, int width,
			int height) {
		GL gl = drawable.getGL();
		// damit keine Verzerrungen enstehen
		ymax = xmax * height / width;
		ymin = -ymax;
		gl.glMatrixMode(GL.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrtho(xmin, xmax, ymin, ymax, zmin, zmax);
		gl.glViewport(0, 0, width, height);
		win_width = width;
		win_height = height;
	}

	public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
			boolean deviceChanged) {
	}


     //  ---------  Window-Events  --------------------

     public void windowClosing(WindowEvent e)
     {  System.exit(0);
     }
     public void windowActivated(WindowEvent e) {  }
     public void windowClosed(WindowEvent e) {  }
     public void windowDeactivated(WindowEvent e) {  }
     public void windowDeiconified(WindowEvent e) {  }
     public void windowIconified(WindowEvent e) {  }
     public void windowOpened(WindowEvent e) {  }

	
//  ---------  Key Events  --------------------

	@Override
	public void keyPressed(KeyEvent e) {
		switch (e.getKeyCode()) {
		case KeyEvent.VK_S:
				if (anim.isAnimating()) {
					anim.stop();
				} else {
					anim.start();
				}
			break;
		case KeyEvent.VK_UP:
			elev++;
			canvas.repaint();
			break;
		case KeyEvent.VK_DOWN:
			elev--;
			canvas.repaint();
			break;
		case KeyEvent.VK_LEFT:
			azim++;
			canvas.repaint();
			break;
		case KeyEvent.VK_RIGHT:
			azim--;
			canvas.repaint();
			break;
		default:
			break;
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {}

	@Override
	public void keyTyped(KeyEvent e) {}
	
	private void log(String msg){
		if (log)
			System.out.println(msg);
	}

	@Override
	public void start() {
		run = true;
		if (!anim.isAnimating()){
//			anim.start();			
		}
	}

	@Override
	public void stop() {
		run = false;
		if (anim.isAnimating()){
//			anim.stop();
		}
	}

	public void modeSwtich(int mode) {
		// TODO Auto-generated method stub
		
	}
  }